#ifndef CACHE_H
#define CACHE_H

using namespace std;

#include <stdlib.h>
#include "enums.h"
#include <stdint.h>
#include <string>
#include <iostream>
#include "ReplacementPolicy.h"
#include <set>
#include <fstream>
#include "pin.H"

#define MAXTHREADS 100
//typedef uint32_t UINT32;
//typedef uint64_t UINT64;
//typedef uint8_t UINT8;
//typedef uint16_t UINT16;

//structure to hold information about the cache.
struct CacheInfo{
    
    string desc;                // cache description for user
    UINT32 size;                // total size of cache, in bytes
    UINT32 blockSize;           // size of a cache line/block in bytes
    UINT32 assoc;               // set associativity of cache. 
    UINT32 numRows;             // calculated value for performance. numRows=size/blockSize/assoc 
    // any object using numRows should calculate it once first.
    string policy;   // replacement policy used by this cache
    
    int selfID; // user defined cache id
    int parentID; // user defined parent id
    bool dump; //wether debug should print arrays or not
    UINT64 debugLimit;//how many instructions to print debug for
    CacheInfo():
    
    desc("unknownDesc"),
    size(0),
    blockSize(0),
    assoc(0),
    numRows(0),
    policy("RoundRobin"),
    selfID(999),
    parentID(999),
    dump(false),
    debugLimit(1000)
    {}
    
}; 

/*
 class contains structural details for each cache, the actual cache memory object, and cache functions
*/

class Cache{
    
public:
    
    //Cache();
    // constructor requires a CacheInfo object. calls initCache
    Cache(CacheInfo info);
    
    // allocate memory for the cache array
    void initCache();
    
    // return a copy of the cache info
    CacheInfo getCacheInfo();
    		
    //search cache is called by the CPU, or by lower caches that missed.
    void searchCache(UINT64 addr, int threadid, bool write, ofstream& debug, UINT64 icount);
    
    //called when parent contains the block, but may not own it. updates status bits and invalidates
    void getOwnership(UINT64 addr,ofstream& debug, UINT64 icount);
    //check cache is called for coherency, and the cache responds with its status
    UINT8 checkCache(UINT64 start, int numBytes, bool invalidate, bool check, ofstream & debug, UINT64 icount);
        
    //seeks Cache to invalidate specified data, and if found, calls invaldiate on children
    void invalidate(UINT64 start, int numBytes, ofstream& debug,UINT64 icount);
    
    //if a sibling calls check Cache(to read or write a block) we have to make sure we don't keep that block in modified.
    void goShared(UINT64 start, int numBytes, ofstream& debug,UINT64 icount);
    //prints statistics array
    void printStats(ofstream& stream, set<int> processed);
    
    void cacheDump(ofstream& out);
    // use this to set pointers to adjacent caches. can not be set at initialization because family not known. MUST BE SET before cache simulation begins.
    void setParent(Cache* parent);
    void setChildren(Cache** children, int numChildren);
    void setSiblings(Cache** siblings, int numSiblings);
    Cache* getParent();
    
    
     
private:
    
    CacheInfo info; //holds the structure containing cache specifications
    
    UINT64 *cache;  //pointer to the array representing this cache
    
    /* status:
     an array of status bit vectors. same number of elements as the cache. 
     the only bits the Cache understands are:
     valid (bit 0)
     exclusive write(bit 1)
     */
     
    UINT8 * status; 
    
    bool output; //already sent output
    bool dump; //whether cache array should be dumped in debug mode
    Cache* parent;
    Cache** children;
    Cache** siblings;
    int numChildren,  numSiblings;
    ReplacementPolicy* policy;
    UINT64 tagMask, indexMask, blockMask; //bit mask vectors to get tag/row
    int indexShift, blockSizeShift;  //shift amount
    UINT64 stats[MAXTHREADS][256];//keep track of stats
        
}; 

#endif
